<?php
/**
 * @property-read bool $templateIsValid Whether or not the template is a valid template.
 * @property-read array $registeredVars The current registered variables
 * @property-read bool $assembled Has this already been assembled?
 * @property-read mixed $viewData The current HTML output
 */
class View {
    private $template;
    protected $templateIsValid = false;
    protected $registeredVars = array();
    protected $assembled = false;

    public $viewData;

    /**
     * Register a template which is a snippet from a PHP code which will output
     * HTML elements and visible properties.
     * @param string $tpl The path of the view template; relative to ../views/ without the .view.php extension
     * @return bool Whether or not the register was successful
     */
    public function registerTemplate($tpl) {
        if (file_exists('views/' . $tpl . '.view.php')) {
            $this->template = $tpl;
            $this->templateIsValid = true;
            return true;
        }
        $this->templateIsValid = false;
        return false;
    }

    /**
     * Check whether or not we have a valid template - A valid template means that
     * we have registered one, and the file exists.
     * @return bool Whether or not the template is valid
     */
    public function isTemplateValid() {
        return $this->templateIsValid;
    }

    /**
     * Register a variable to be loaded into the template. This means that we
     * can pass global variables into the template itself before it's assembled.
     * @param mixed $var The value of the variable
     * @param string $name The name of the variable
     * @return bool Whether or not it succeeded
     */
    public function registerVariable($var,$name) {
        $this->registeredVars[$name] = $var;
        return true;
    }

    /**
     * Register another value into the global array with the name passed by $name.
     * @param mixed $var The value of a new indexed variable in the array.
     * @param string $name The name of the array
     * @return bool Whether or not it succeeded
     */
    public function registerVariableToArray($var,$name) {
        $this->registeredVars[$name][] = $var;
        return true;
    }

    /**
     * Extract the registered variables then build the view into memory.
     * @return bool Whether or not the view is now assembled
     */
    public function assembleView() {
        if (!$this->assembled && $this->templateIsValid) {
            extract($this->registeredVars,EXTR_SKIP);
            ob_start();
            include('views/' . $this->template . '.view.php');
            $this->viewData = ob_get_clean();
            $this->assembled = true;
        }
        return true;
    }

}

class ViewManager extends View {
    private $views;
    private $currentViewIndex = 0;
    private $currentView;
    private $frame = 'default';
    private static $instance;
    private $styles = array();
    private $scripts = array();

    public $pageData;

    /**
     * This function registers a new view object and stores the previous one in
     * memory if anything was loaded.
     * @param string $tpl The name of the template to automatically register
     * @return View The new view object
     */
    public function registerNewView($tpl = null) {
        $this->views[$this->currentViewIndex] = new View();
        $this->currentView = &$this->views[$this->currentViewIndex];
        $this->currentViewIndex++;
        $r = &$this->currentView;
        if (isset($tpl)) {
            $r->registerTemplate($tpl);
        }
        return $r;
    }

    /**
     * This function should be used to register any JavaScript script when the page is assembled.
     * @param string $path The path to the javascript script; relative to ../scripts/ without the .js extention
     * @return bool Whether or not the script was registered successfully
     */
    public function registerScript($path) {
        $this->scripts[] = $path;
        return true;
    }

    /**
     * Register any Cascading Style Scripts when the page is assembled.
     * @param string $path The path to the CSS document; relative to ../css/ without the .css extension
     * @return bool Whether or not the style was registered.
     */
    public function registerStyle($path) {
        $this->styles[] = $path;
        return true;
    }

    /**
     * Register a template which is a snippet from a PHP code which will output
     * HTML elements and visible properties.
     * @param string $tpl The path of the view template; relative to ../views/ without the .view.php extension
     * @return bool Whether or not the register was successful
     */
    public function registerTemplate($tpl) {
        return $this->registerNewView($tpl);
    }

    /**
     * Register a variable to be loaded into the template. This means that we
     * can pass global variables into the template itself before it's assembled.
     * @param mixed $var The value of the variable
     * @param string $name The name of the variable
     * @return bool Whether or not it succeeded
     */
    public function registerVariable($var,$name) {
        if (!$this->currentView)
        {
            $this->registerNewView();
        }
        $this->currentView->registerVariable($var, $name);
    }

    /**
     * Register a frame. A frame is the basic outline of the web page and should
     * contain the page container and echo the output from the assembled views.
     * @param string $frame The name of a frame
     * @return bool Whether or not the frame was successfully registered
     */
    public function registerFrame($frame) {
        if (file_exists('frames/' . $frame . '.frame.php')) {
            $this->frame = $frame;
            return true;
        }
        return false;
    }

    /**
     * @ignore
     */
    public function assembleView() {
        return false;
    }

    /**
     * Assemble each view object then output it to the screen as a viewable
     * object.
     * @return bool Whether or not the page was successfully assembleded
     */
    public function assemblePage() {
        if (!$this->assembled) {
            $views = array();
            if (isset($this->views) && is_array($this->views)) {
                foreach ($this->views as $__view) {
                    $__view->assembleView();
                    if ($__view->templateIsValid) {
                        $views[] = $__view->viewData;
                    }
                }
                $this->registerVariable($views, "views");
            }
            else {
                $this->registerVariable(array(), "views");
            }

            if (is_array($this->registeredVars)) {
                extract($this->registeredVars,EXTR_SKIP);
            }
            $this->assembled = true;

            //Assemble links of stylesheets
            $styleSheets = '';
            foreach ($this->styles as $__S) {
                $__SURL = URL('css',$__S . '.css');
                $styleSheets .= ("@import url(" . $__SURL . ");\n");
            }
            $styleSheets = "<style type=\"text/css\">$styleSheets</style>";

            //Assemble links of JS as needed;
            $scripts = '';
            if (isset($this->scripts) && is_array($this->scripts)) {
                foreach ($this->scripts as $__S) {
                    $__SURL = URL('script',$__S . '.js');
                    $scripts .= "<script type=\"text/javascript\" src=\"$__SURL\"></script>\n";
                }
            }
            ob_start();
            include('frames/' . $this->frame . '.frame.php');
            $this->pageData = ob_get_clean();
            //unset($views);
            //unset($styles);
            //unset($scripts);
        }
        return true;
    }

    private function __construct() {
    }

    /**
     * Acquire the singleton object of the ViewManager object. If one doesn't exists
     * yet, create it.
     * @return ViewManager A singleton of the ViewManager object
     */
    public static function inst() {
        if (!isset(self::$instance)) {
            $c = __CLASS__;
            self::$instance = new $c;
        }
        return self::$instance;
    }
}

?>